﻿using System;
using System.ComponentModel;

namespace Simp.CodeModel
{
	/// <summary>Holds a call to a bound, or resolved, function.</summary>
	[ImmutableObjectAttribute(true)]
	public class BoundFunctionCalling : ParameterLessFunctionBase, IFunctionSerializer
	{
		private readonly IFunction _innerFunction;
		private readonly IFunction[] _parameters;

		/// <summary>Constructor taking the function name and its parameters.</summary>
		/// <param name="innerFunction"></param>
		/// <param name="parameters"></param>
		public BoundFunctionCalling(IFunction innerFunction, params IFunction[] parameters)
		{
			if (innerFunction == null)
			{
				throw new ArgumentNullException("function");
			}
			if (parameters == null)
			{
				throw new ArgumentNullException("parameters");
			}
			_innerFunction = innerFunction;
			_parameters = parameters;
		}

		#region IFunctionSerializer Members
		void IFunctionSerializer.Serialize(AppendOnlyStringBuilder builder, params IFunction[] parameters)
		{
			IFunctionSerializer innerFunctionSerializer = _innerFunction as IFunctionSerializer;

			if (innerFunctionSerializer == null)
			{
				builder.Append(FunctionName);
				if (FunctionParameters.Length > 0)
				{
					builder.Append("(");
					for (int i = 0; i != FunctionParameters.Length; ++i)
					{
						IFunction param = FunctionParameters[i];

						FunctionHelper.Serialize(param, builder);
						if (i != FunctionParameters.Length - 1)
						{
							builder.Append(", ");
						}
					}
					builder.Append(")");
				}
			}
			else
			{
				innerFunctionSerializer.Serialize(builder, _parameters);
			}
		}
		#endregion

		/// <summary>Returns the inner function.</summary>
		public IFunction InnerFunction
		{
			get { return _innerFunction; }
		}

		/// <summary>Parameters to apply on the function.</summary>
		public IFunction[] FunctionParameters
		{
			get { return _parameters; }
		}

		/// <summary>Name of the underlying function.</summary>
		public override string FunctionName
		{
			get { return _innerFunction.FunctionName; }
		}

		/// <summary>Accepts the visitor and let it visit each parameters.</summary>
		/// <param name="visitor"></param>
		/// <returns></returns>
		public override IFunction AcceptVisitor(IFunctionVisitor visitor)
		{
			bool hasChanged = false;
			IFunction[] newParameters = new IFunction[FunctionParameters.Length];

			for (int i = 0; i != newParameters.Length; ++i)
			{
				var param = FunctionParameters[i];
				var newValue = visitor.Visit(param);

				if (newValue != null)
				{
					newParameters[i] = newValue;
					hasChanged = true;
				}
				else
				{
					newParameters[i] = param;
				}
			}

			if (hasChanged)
			{
				return new BoundFunctionCalling(_innerFunction, newParameters);
			}
			else
			{	//	Nothing has changed
				return null;
			}
		}

		/// <summary>Attempts to apply the function parameters on the inner function after simplifying all the parameters.</summary>
		/// <param name="simplificationMode"/>
		/// <returns></returns>
		protected override IFunction Simplify(SimplificationMode simplificationMode)
		{
			if (FunctionParameters.Length == 0)
			{	//	No parameters, this function is basically a variable
				IFunction newFunction = _innerFunction.Simplify(simplificationMode);

				if (newFunction == null)
				{
					if (_innerFunction is IKeepOnBoundFunctionCalling)
					{	//	Marker interface forces its non-simplification
						return null;
					}
					else
					{
						return _innerFunction;
					}
				}
				else
				{
					if (newFunction is IKeepOnBoundFunctionCalling)
					{	//	Marker interface forces its non-simplification
						return new BoundFunctionCalling(newFunction);
					}
					else
					{
						return newFunction;
					}
				}
			}
			else
			{	//	Simplifies the parameters
				bool hasChanged = false;
				IFunction[] newParameters = new IFunction[FunctionParameters.Length];

				for (int i = 0; i != newParameters.Length; ++i)
				{
					var param = FunctionParameters[i];
					var newValue = param.Simplify(simplificationMode, FunctionHelper.EmptyParameterValues);

					if (newValue != null)
					{
						newParameters[i] = newValue;
						hasChanged = true;
					}
					else
					{
						newParameters[i] = param;
					}
				}
				IFunction newFunction = _innerFunction.Simplify(simplificationMode, newParameters);

				if (newFunction != null)
				{
					return newFunction;
				}
				else
				{
					if (hasChanged)
					{
						return new BoundFunctionCalling(_innerFunction, newParameters);
					}
					else
					{	//	Nothing has changed
						return null;
					}
				}
			}
		}
	}
}